import tensorflow as tf

'''
交叉熵实验
有一个便签值 labels 和一个网络输出值 logits
1）两次 softmax 实验： 将输出值 logits 分别进行1次和2次softmax，观察两次的区别及意义
2）观察交叉熵：将 1）中的两个值分别进行 softmax_cross_entropy_with_logits 观察他们的区别
3）自建公式实验：将做2次softmax的值放到自建组合的公式中得到正确的值
'''
labels = [[0, 0, 1], [0, 1, 0]]
logits = [[2, 0.5, 6], [0.1, 0, 3]]

logits_scaled = tf.nn.softmax(logits)
logits_scaled2 = tf.nn.softmax(logits_scaled)

result1 = tf.nn.softmax_cross_entropy_with_logits(logits=logits, labels=labels)
result2 = tf.nn.softmax_cross_entropy_with_logits(logits=logits_scaled, labels=labels)
result3 = -tf.reduce_sum(labels*tf.log(logits_scaled), 1)

with tf.Session() as sess:
    print('scaled= ', sess.run(logits_scaled))
    # scaled=  [[0.01791432 0.00399722 0.97808844] [0.04980332 0.04506391 0.90513283]]

    # 经过第二次的 softmax 后，分布概率会有变化
    print('scaled2= ', sess.run(logits_scaled2))
    # scaled2 = [[0.21747023 0.21446465 0.56806517] [0.2300214  0.22893383 0.5410447]]

    # 正确方式
    print('result1', sess.run(result1), '\n')   # result1 [0.02215516 3.0996735 ]

    # 如果将 softmax 变换完的值放进去，就相当于第二次 softmax 的loss, 所以会出错
    print('result2', sess.run(result2), '\n')   # result2 [0.56551915 1.4743223 ]

    print('result3', sess.run(result3), '\n')   # result3 [0.02215518 3.0996735 ]

'''
1）可以看到 logis 中的值原本加和都是大于1的，经过 softmax 之后, 总和为1
2）样本中第一个与分类标签相符，第二个不相符，所以 resulrt1 结果是 第一个的交叉熵比较小，第二个比较大
3）从 result1 和 result2 可以看出，传入 softmax_cross_entropy_with_logits 的 logits 是不需要进行softmax
   如果将softmax后得scaled传入softmax_cross_entropy_with_logits就等于进行了两次softmax转化 
   也就是说 tf 中的 softmax_cross_entropy_with_logits 函数会进行一次 softmax 操作
4）对于用 softmax 转化后得scaled， 在计算时就不能再用tf里面的softmax_cross_entropy_with_logits了，
  可以自己组合 例如： result3 也可以得到正确结果 
'''

'''
one_hot实验
对非 one_hot 编码为标签的数据进行交叉熵运算，比较其与one_hot编码的交叉熵之间的差别
'''
# 标签总概率为1
labels = [[0.4, 0.1, 0.5], [0.3, 0.6, 0.1]]
result4 = tf.nn.softmax_cross_entropy_with_logits(logits=logits, labels=labels)
with tf.Session() as sess:
    # result4:  [2.1721554 2.7696736]   对于正确分类的交叉熵与错误分类的交叉熵，二者差别没有 one_hot 编码那么明显
    print('result4: ', sess.run(result4), '\n')

'''
sparse交叉熵的使用
使用 sparse_softmax_cross_entropy_with_logits 函数，
对于非 one_hot 的标签进行交叉熵计算，比较其与 one_hot 标签在使用上的区别
'''

# sparse 标签
labels = [2, 1]  # 表明 lables 中一共分为3个类： 0， 1， 2. [2, 1] == one_hot编码中的 001 和 010
result5 = tf.nn.sparse_softmax_cross_entropy_with_logits(logits=logits, labels=labels)
with tf.Session() as sess:
    print('result5: ', sess.run(result5), '\n')  # result5:  [0.02215516 3.0996735 ] 与 result1 完全一样

'''
计算loss值
1）对于 softmax_cross_entropy_with_logits 后得结果求loss直接取均值
2）对于 softmax 后的结果使用 -tf.reduce_sum(labels*tf.log(logits_scaled)) 求均值
3）对于 softmax 后的结果使用 -tf.reduce_sum(labels*tf.log(logits_scaled)， 1) 
   等同于 softmax_cross_entropy_with_logits 结果
4）由1）和3 ）可以推出对3）进行求均值也可以得出正确的loss值，
   tf.reduce_mean(-tf.reduce_sum(labels*tf.log(logits_scaled)， 1)) = loss
'''
loss = tf.reduce_mean(result1)
labels = [[0, 0, 1], [0, 1, 0]]
loss2 = tf.reduce_mean(-tf.reduce_sum(labels*tf.log(logits_scaled), 1))
with tf.Session() as sess:
    print('loss: ', sess.run(loss))
    print('loss2: ', sess.run(loss2))